#pragma once

#include "MatrixTypes.h"
#include "ScalarTypes.h"

namespace SL
{

namespace math
{
/*
    we use COLUMN math
    new_vector = transform * vector (multiply matrix on left)
*/

/*
 right-handed : forward is -Z
 left-handed : forward is +Z
*/
inline float4x4 matrixLookTo(const float3& eye, const float3& eye_dir, const float3 up, Handedness handness = Handedness::LeftHanded)
{
    float3 z = handness == Handedness::LeftHanded ? eye_dir.normalized() : -eye_dir.normalized();
    float3 x = up.cross(z).normalized();
    float3 y = z.cross(x).normalized();

    float4x4 mat = float4x4::Identity();
    mat(0, 0) = x.x(), mat(0, 1) = x.y(), mat(0, 2) = x.z();
    mat(1, 0) = y.x(), mat(1, 1) = y.y(), mat(1, 2) = y.z();
    mat(2, 0) = z.x(), mat(2, 1) = z.y(), mat(2, 2) = z.z();

    mat(0, 3) = -x.dot(eye);
    mat(1, 3) = -y.dot(eye);
    mat(2, 3) = -z.dot(eye);

    return mat;
}

inline float4x4 matrixLookAt(const float3& eye, const float3& center, const float3 up, Handedness handness = Handedness::LeftHanded)
{
    float3 eyeDir = center - eye;
    return matrixLookTo(eye, eyeDir, up, handness);
}

/*
aspect is width / height
*/
inline float4x4 perspectiveFov(float fovY, float aspect, float nearZ, float farZ, Handedness handness = Handedness::LeftHanded)
{
    float oneOverTanHalfFovy = 1.0f / std::tan(fovY * 0.5f);
    float fRange = farZ / (farZ - nearZ);
    float4x4 mat = float4x4::Zero();

    mat(0, 0) = oneOverTanHalfFovy / aspect;
    mat(1, 1) = oneOverTanHalfFovy;
    mat(2, 2) = fRange;
    mat(2, 3) = -fRange * nearZ;
    mat(3, 2) = 1.0f;

    return mat;
}

inline float4x4 orthographic(float viewWidth, float viewHeight, float nearZ, float farZ, Handedness handness = Handedness::LeftHanded)
{
    float4x4 mat = float4x4::Zero();
    float fRange = 1.0f / (farZ - nearZ);

    mat(0, 0) = 2.0f / viewWidth;
    mat(1, 1) = 2.0f / viewHeight;
    mat(2, 2) = fRange;
    mat(2, 3) = -fRange * nearZ;
    mat(3, 3) = 1.0f;

    return mat;
}

// UNCHECKED
inline float4x4 perspectiveOffCenter(
    float viewLeft,
    float viewRight,
    float viewBottom,
    float viewTop,
    float nearZ,
    float farZ,
    Handedness handness = Handedness::LeftHanded
)
{
    float4x4 mat = float4x4::Zero();
    float twoNearZ = 2.0f * nearZ;
    float reciprocalWidth = 1.0f / (viewRight - viewLeft);
    float reciprocalHeight = 1.0f / (viewTop - viewBottom);
    float fRange = farZ - nearZ;

    mat(0, 0) = twoNearZ * reciprocalWidth;
    mat(1, 1) = twoNearZ * reciprocalHeight;
    mat(2, 0) = -(viewRight + viewLeft) * reciprocalWidth;
    mat(2, 1) = -(viewTop + viewBottom) * reciprocalHeight;
    mat(2, 2) = fRange;
    mat(2, 3) = 1.0f;
    mat(3, 2) = -fRange * nearZ;

    return mat;
}
} // namespace math

} // namespace SL